package com.prpat;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.net.Socket;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import de.uniba.wiai.lspi.util.logging.SystemOutPrintlnLogger;

class MessageHandler implements Runnable
{
	public static final String path = "/opt/john-1.7.9-jumbo-6/run/john";

	public static SystemOutPrintlnLogger Logger = new SystemOutPrintlnLogger("MessageHandler.class");
	
	private Message message;
	private Socket incoming;

	/**
	 * {@link ObjectInputStream} to read {@link Message messages} from.
	 */
	private ObjectInputStream in;

	MessageHandler(Socket clientSocket)
	{
		incoming = clientSocket;
	}

	private void handleMessage() 
	{
		Logger.info("HandleMessage()");
		
		if(CrackedHashes.alreadyCracked(message.getMessage())){
			return;
		}
		switch(message.getType())
		{
			case Message.REQUEST:
			
				//GUI
				//				River.display.append("Received request from : \n" + message + "\n");
				System.out.println("Received REQUEST from : \n" + message.getSource() + "\n");
				handleRequest();
				
			break;
			case Message.REPLY:
			
				//GUI
				//				River.display.append("Received reply : " + message + "\n");
				System.out.println("Received  REPLY: " + message + "\n");
				handleReply();
				
			break;
			
			default:
			break;
		}

	}

	private void handleRequest() 
	{
		Logger.info("HandleRequest()");

		//TODO if the server got a request
		if(River.iAmServer()){
			StateTrans.enqRange(message.getRange());
			ServerWait.set(false);
			System.err.println("MessageHandler, handleRequest, server wait");
			//			PendingContainer.insert(message);
			return;
		}

		//		TaskCounter.increment();

		String hash = "files/" + message.getMessage() + message.range.level + message.range.start;

		createFile(hash);
		appendToFile(hash);
		String sessionName = "files/" +message.range.level+":"+message.range.start;

		Logger.info("Running John: " + new String[] {path , "--session="+sessionName, "--markov:"+message.range.level+":"+
				message.range.start+":"+message.range.end+":"+8+"" ,"--format:raw-md5",hash});

		String ans = runJohn(new String[] {path , "--session="+sessionName, "--markov:"+message.range.level+":"+
				message.range.start+":"+message.range.end+":"+8+"" ,"--format:raw-md5",hash});

		if(ans.length()>1){
			String reply = checkIfCracked(ans);
			if(reply.contains("Cracked:"))
			{
				Logger.info("Found password: " + message.getMessage());
				CrackedHashes.addToCracked(message.getMessage());
			}

			Logger.info("Replying Bootsrap with messge REPLY");
			Message mes = new Message(reply,message.getRange(), Message.REPLY);		
			Sender.replyToBootstrap(mes);
		}

		ans = terminal(new String[] {"rm", "-f", hash});
		ans = terminal(new String[] {"rm", "-f",sessionName+".log"});
		ans = terminal(new String[] {"rm", "-f",sessionName+".rec"});
		
		TaskCounter.decrement();

	}


	private String checkIfCracked(String johnOutput)
	{
		try 
		{
			Pattern is_cracked_pattern = Pattern.compile("guesses: \\d");
			Matcher is_matched = is_cracked_pattern.matcher(johnOutput);

			// In case password cracked
			if ( is_matched.find() && !johnOutput.contains("guesses: 0") )
			{
				return 
						"Cracked" +
						":" 	  +
						johnOutput.split("\n")[1].split("\\s")[0] +			 // password
						"\n" 	  +
						message.getMessage()
						;
			}
		}
		catch (Exception e){
			return message.getMessage();
		}
		return message.getMessage();
	}

	private void handleReply()
	{
		if(message.getMessage().contains("Cracked:"))//hash was broken
		{
			StringBuilder res = new StringBuilder(message.getMessage());
			res.substring(res.indexOf(":"), res.indexOf("\n"));
			StateTrans.sealHash(message.toString(), res.substring(res.indexOf(":")+1, res.indexOf("\n")), message.getSource());

		}
		else{
			PendingContainer.extract(message.range);
		}
	}

	private void appendToFile(String name) 
	{
		try 
		{
			FileWriter fstream = new FileWriter(name, true);
			BufferedWriter out = new BufferedWriter(fstream);
			out.write(message.getMessage());
			//Close the output stream
			out.close();
		} 
		catch (IOException e){
			System.err.println(this.getClass().getName() + ", appendToFile");
		}

	}

	private void createFile(String name)
	{
		File f;
		Logger.info("Creating File: " + name);
		
		f = new File(name);
		boolean exists = f.exists();
		if(exists){
			terminal(new String[]{"rm", "-f", name});
			exists = f.exists();
		}
		if(!exists)
		{
			try{
				f.createNewFile();
			} catch (IOException e) {
				System.err.println(this.getClass().getName() + ", createFile, e");
			}
		}
	}

	private String terminal(String[] args)
	{
		StringBuilder output = new  StringBuilder();
		try 
		{
			Process child = Runtime.getRuntime().exec(args);

			// Get the input stream and read from it the command output
			InputStream in = child.getInputStream();
			InputStream er = child.getErrorStream();
			int c;

			while ((c = in.read()) != -1) 
				output.append((char)c);
			in.close();

			while ((c = er.read()) != -1) 
				output.append((char)c);
			er.close();
		}
		catch (IOException e) 
		{
			System.err.println("MessageHandler, terminal, " + e.getMessage());
		}
		return output.toString();
	}
	
	private String runJohn(String[] args)
	{
		StringBuilder output = new  StringBuilder();
		try 
		{
			Process child = Runtime.getRuntime().exec(args);

			// Get the input stream and read from it the command output
			InputStream in = child.getInputStream();
			InputStream er = child.getErrorStream();
			int c;

			while ((c = in.read()) != -1) 
				output.append((char)c);
			
			Logger.info("john STDOUT:" + output);
			in.close();

			while(!output.toString().contains("guesses:")){
				while ((c = er.read()) != -1) {
					output.append((char)c);
				}

				Logger.info("John Output: " + output);
				
				if(!output.toString().contains("MKV")){
					return "";
				}
			}
			
			er.close();
		}
		catch (IOException e) 
		{
			Logger.error("MessageHandler, terminal, " + e.getMessage());
		}
		
		return output.toString();
	}


	/**
	 * 
	 */
	public void run() 
	{
		Logger.info("Starting MessageHandler thread");
		
		try 
		{
			in = new ObjectInputStream(incoming.getInputStream());
			message = (Message) in.readObject();
			in.close();

//			if(!River.iAmServer()){
//				if(TaskCounter.count() > 2)
//					Sender.sendMessage(message);
//				else
//					handleMessage();
//			}
//			else
		
			handleMessage();

		} 
		catch (IOException ioe) {
			Logger.error(ioe.getMessage());
		} 
		catch (ClassNotFoundException cnfe) {
			Logger.error(cnfe.getMessage());
		}
	}

}
